// Package gocelery provides the main API for the GoCelery library
package gocelery

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"gitee.com/yuebanlaosiji/gocelery/pkg/backend"
	"gitee.com/yuebanlaosiji/gocelery/pkg/broker"
	"gitee.com/yuebanlaosiji/gocelery/pkg/client"
	"gitee.com/yuebanlaosiji/gocelery/pkg/protocol"
	"gitee.com/yuebanlaosiji/gocelery/pkg/registry"
	"gitee.com/yuebanlaosiji/gocelery/pkg/serializer"
	"gitee.com/yuebanlaosiji/gocelery/pkg/types"
	"gitee.com/yuebanlaosiji/gocelery/pkg/worker"
)

// Client provides the client API for submitting tasks
type Client interface {
	// Send a task to be executed by workers
	SendTask(name string, args []interface{}, kwargs map[string]interface{}) (string, error)

	// Send a task with additional options
	SendTaskWithOptions(name string, args []interface{}, kwargs map[string]interface{}, options *TaskOptions) (string, error)

	// Get the result of a task
	GetResult(taskID string) (interface{}, error)

	// Wait for a task result with a timeout
	WaitForResult(taskID string, timeout time.Duration) (interface{}, error)

	// Close the client
	Close() error
}

// Worker provides the worker API for processing tasks
type Worker interface {
	// Register a task handler
	Register(name string, handler types.TaskFunc) error

	// Start the worker
	Start() error

	// Stop the worker
	Stop() error

	// Get the worker status
	Status() worker.WorkerStatus
}

// TaskOptions provides additional options when sending a task
type TaskOptions struct {
	TaskID    string
	Retries   int
	Countdown int
	ETA       *time.Time
	ExpiresAt *time.Time
	Queue     string
}

// Config provides configuration for GoCelery clients and workers
type Config struct {
	BrokerURL    string
	BackendURL   string
	DefaultQueue string
	ResultExpiry time.Duration
	Concurrency  int
}

// Errors
var (
	ErrInvalidBrokerURL  = errors.New("invalid broker URL")
	ErrInvalidBackendURL = errors.New("invalid backend URL")
)

// ClientImpl is the concrete implementation of the Client interface
type ClientImpl struct {
	rawClient client.Client
	broker    broker.Broker
	backend   backend.Backend
	protocol  protocol.Protocol
	queue     string
}

// SendTask sends a task to be executed by workers
func (c *ClientImpl) SendTask(name string, args []interface{}, kwargs map[string]interface{}) (string, error) {
	task := types.Task{
		Name:   name,
		Args:   args,
		Kwargs: kwargs,
	}

	return c.rawClient.SendTask(task)
}

// SendTaskWithOptions sends a task with additional options
func (c *ClientImpl) SendTaskWithOptions(name string, args []interface{}, kwargs map[string]interface{}, options *TaskOptions) (string, error) {
	task := types.Task{
		Name:   name,
		Args:   args,
		Kwargs: kwargs,
	}

	if options != nil {
		task.ID = options.TaskID
		task.Retries = options.Retries
		task.ETA = options.ETA
		task.ExpiresAt = options.ExpiresAt
	}

	return c.rawClient.SendTask(task)
}

// GetResult gets the result of a task
func (c *ClientImpl) GetResult(taskID string) (interface{}, error) {
	return c.rawClient.GetResult(taskID)
}

// WaitForResult waits for a task result with a timeout
func (c *ClientImpl) WaitForResult(taskID string, timeout time.Duration) (interface{}, error) {
	return c.rawClient.WaitForResult(taskID, timeout)
}

// Close closes the client
func (c *ClientImpl) Close() error {
	var errs []error

	if err := c.broker.Close(); err != nil {
		errs = append(errs, fmt.Errorf("failed to close broker: %w", err))
	}

	if c.backend != nil {
		if err := c.backend.Close(); err != nil {
			errs = append(errs, fmt.Errorf("failed to close backend: %w", err))
		}
	}

	if len(errs) > 0 {
		return fmt.Errorf("errors closing client: %v", errs)
	}

	return nil
}

// WorkerImpl is the concrete implementation of the Worker interface
type WorkerImpl struct {
	rawWorker worker.Worker
	broker    broker.Broker
	backend   backend.Backend
}

// Register registers a task handler
func (w *WorkerImpl) Register(name string, handler types.TaskFunc) error {
	return w.rawWorker.Register(name, handler)
}

// Start starts the worker
func (w *WorkerImpl) Start() error {
	return w.rawWorker.Start()
}

// Stop stops the worker
func (w *WorkerImpl) Stop() error {
	return w.rawWorker.Stop()
}

// Status gets the worker status
func (w *WorkerImpl) Status() worker.WorkerStatus {
	return w.rawWorker.Status()
}

// NewClient creates a new GoCelery client
func NewClient(config Config) (Client, error) {
	// Create broker
	brk, err := CreateBroker(config.BrokerURL)
	if err != nil {
		return nil, err
	}

	// Create backend (optional)
	var bck backend.Backend
	if config.BackendURL != "" {
		bck, err = CreateBackend(config.BackendURL)
		if err != nil {
			return nil, err
		}
	}

	// Create protocol
	proto := protocol.NewCeleryProtocol()

	// Create queue name
	queueName := "celery"
	if config.DefaultQueue != "" {
		queueName = config.DefaultQueue
	}

	// Create raw client
	clientOptions := &client.ClientOptions{
		QueueName: queueName,
	}

	rawClient, err := client.NewClient(brk, bck, proto, clientOptions)
	if err != nil {
		return nil, err
	}

	return &ClientImpl{
		rawClient: rawClient,
		broker:    brk,
		backend:   bck,
		protocol:  proto,
		queue:     queueName,
	}, nil
}

// NewWorker creates a new GoCelery worker
func NewWorker(config Config) (Worker, error) {
	// Create broker
	brk, err := CreateBroker(config.BrokerURL)
	if err != nil {
		return nil, err
	}

	// Create backend (optional)
	var bck backend.Backend
	if config.BackendURL != "" {
		bck, err = CreateBackend(config.BackendURL)
		if err != nil {
			return nil, err
		}
	}

	// Create registry
	reg := registry.NewMemoryRegistry()

	// Create protocol
	proto := protocol.NewCeleryProtocol()

	// Create serializer
	ser := serializer.NewJSONSerializer()

	// Create queue name
	queueName := "celery"
	if config.DefaultQueue != "" {
		queueName = config.DefaultQueue
	}

	// Create concurrency
	concurrency := 1
	if config.Concurrency > 0 {
		concurrency = config.Concurrency
	}

	// Create worker options
	workerOptions := &worker.WorkerOptions{
		QueueName:   queueName,
		Concurrency: concurrency,
	}

	// Create raw worker
	rawWorker, err := worker.NewWorker(brk, bck, reg, proto, ser, workerOptions)
	if err != nil {
		return nil, err
	}

	return &WorkerImpl{
		rawWorker: rawWorker,
		broker:    brk,
		backend:   bck,
	}, nil
}

// CreateBroker creates a broker instance based on the URL scheme
func CreateBroker(url string) (broker.Broker, error) {
	if strings.HasPrefix(url, "redis://") {
		brk := broker.NewRedisBroker()
		if err := brk.Initialize(url); err != nil {
			return nil, fmt.Errorf("failed to initialize Redis broker: %w", err)
		}
		return brk, nil
	}

	return nil, fmt.Errorf("%w: unsupported broker type: %s", ErrInvalidBrokerURL, url)
}

// CreateBackend creates a result backend instance based on the URL scheme
func CreateBackend(url string) (backend.Backend, error) {
	if strings.HasPrefix(url, "redis://") {
		bck := backend.NewRedisBackend()
		if err := bck.Initialize(url); err != nil {
			return nil, fmt.Errorf("failed to initialize Redis backend: %w", err)
		}
		return bck, nil
	}

	return nil, fmt.Errorf("%w: unsupported backend type: %s", ErrInvalidBackendURL, url)
}

// CreateSerializer creates a serializer based on the content type
func CreateSerializer(contentType string) (serializer.Serializer, error) {
	if contentType == "application/json" || contentType == "" {
		return serializer.NewJSONSerializer(), nil
	}

	return nil, fmt.Errorf("unsupported content type: %s", contentType)
}
